package com.h2.ref.server.user.openid;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.h2.com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import com.h2.com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
import com.h2.ref.server.user.model.User;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.io.xml.XppDriver;

public class OpenIdDaoXml implements IOpenIdDao {
   
   private static final Logger LOG = LoggerFactory.getLogger(
         OpenIdDaoXml.class);
   
   ////
   
   private String _fileDir;
   
   private String _fileName = "openId";
   
   private String _fileExt = ".xml";
   
   private Boolean _writeToFile = true;
   
   ////
   
   /** Map<uid, OpenId> */
   private Map<String, OpenId> _openIds;
   
   /** Map<userId, Collection<uid>> */
   private Map<Integer, Collection<String>> _openIdByUser;
   
   ////
   ////
   
   public void init() {
      
      Collection<OpenId> openIds = read();
      if(openIds == null) {
         setOpenIds(new HashMap<String, OpenId>());
      }
      setOpenIds(new LinkedHashMap<String, OpenId>(openIds.size()));
      setOpenIdByUser(new HashMap<Integer, Collection<String>>());
      
//      else {
//         String uid;
//         Map<String, OpenId> results = new LinkedHashMap<String, OpenId>();
//         for (OpenId openId : openIds) {
//            uid = openId.getUid();
//            results.put(uid, openId);
//            if (!getOpenIdByUser().containsKey(openId.getUserId())) {
//               getOpenIdByUser().put(openId.getUserId(),
//                     new ArrayList<String>());
//            }
//            getOpenIdByUser().get(openId.getUserId()).add(uid);
//         }
//         setOpenIds(results);
//      }
      for (OpenId openId : openIds) {
         addOpenId(openId, false);
      }
   }
   
   @Override
   public Collection<OpenId> findOpenIds(User user) {
      if (user == null || user.getId() == null) {
         return Collections.emptyList();
      }
      Collection<String> uids = getOpenIdByUser().get(user.getId());
      if (uids == null || uids.isEmpty()) {
         return Collections.emptyList();
      }
      Collection<OpenId> results = new ArrayList<OpenId>(uids.size());
      for (String uid : uids) {
         results.add(getOpenIds().get(uid));
      }
      return results;
   }
   
   @Override
   public void addOpenId(OpenId openId) {
      addOpenId(openId, true);
   }
   
   public void addOpenId(OpenId openId, boolean writeOnFinish) {
      if (getOpenIds().containsKey(openId.getUid())) {
         return;
      }
      String uid = openId.getUid();
      getOpenIds().put(uid, openId);
      if (!getOpenIdByUser().containsKey(openId.getUserId())) {
         getOpenIdByUser().put(openId.getUserId(),
               new ArrayList<String>());
      }
      getOpenIdByUser().get(openId.getUserId()).add(uid);
      if (writeOnFinish) {
         write();
      }
   }
   
   @Override
   public void deleteOpenId(OpenId openId) {
      deleteOpenId(openId, true);
   }
   
   public void deleteOpenId(OpenId openId, boolean writeOnFinish) {
      getOpenIds().remove(openId.getUid());
      if (getOpenIdByUser().containsKey(openId.getUserId())) {
         getOpenIdByUser().get(openId.getUserId()).remove(openId.getUid());
      }
      if (writeOnFinish) {
         write();
      }
   }
   
   @Override
   public void deleteUser(User user) {
      Collection<OpenId> openIds = findOpenIds(user);
      if (openIds == null || openIds.isEmpty()) {
         return;
      }
      for (OpenId openId : openIds) {
         deleteOpenId(openId, false);
      }
      write();
   }

   /* (non-Javadoc)
    * @see com.h2.tac.server.user.openid.IOpenIdDao#findByIdentityUrl(
    *    java.lang.String)
    */
   @Override
   public OpenId findByIdentityUrl(String identityUrl) {
      return getOpenIds().get(identityUrl);
   }
   
   public void write() {
      if (!getWriteToFile()) {
         return;
      }
      LOG.info("Writting DAO.");
      
      File file = new File(getFileDir(), getFileName() + getFileExt());
      
      XStream xstream = new XStream();
      applyAlias(xstream);
      
      String xml = xstream.toXML(new ArrayList<OpenId>(getOpenIds().values()));
      
      FileOutputStream fos = null;
      try {
         fos = new FileOutputStream(file);
         
         fos.write(xml.getBytes());
         fos.flush();
         fos.close();
         
         LOG.debug("File has been saved: " + file);
      } catch (IOException exp) {
         LOG.error(exp.getMessage());
         LOG.debug("Details: ", exp);
      }
   }
   
   @SuppressWarnings("unchecked")
   public Collection<OpenId> read() {
      LOG.info("Loading DAO.");
      
      File file = new File(getFileDir(), getFileName() + getFileExt());
      if(!file.exists()) {
         LOG.info("File does not exist: " + file);
         return null;
      }
      
      FileInputStream fis = null;
      XStream xstream = new XStream(
            new PureJavaReflectionProvider(new FieldDictionary()),
            new XppDriver(new XmlFriendlyReplacer("_-", "")));
      applyAlias(xstream);
      
      Collection<OpenId> openIds = null;
      try {
         fis = new FileInputStream(file);
         openIds = (Collection<OpenId>)xstream.fromXML(fis);
         LOG.debug("DAO has been loaded: " + file);
         return openIds;
      } catch (IOException exp) {
         LOG.error(exp.getMessage());
         LOG.error("Details: ", exp);
         return null;
      }
   }
   
   private void applyAlias(XStream xstream) {
      xstream.alias("openId", OpenId.class);
   }

   /**
    * @return the fileDir
    */
   public String getFileDir() {
      return _fileDir;
   }

   /**
    * @param fileDir the fileDir to set
    */
   public void setFileDir(String fileDir) {
      _fileDir = fileDir;
   }

   /**
    * @return the fileExt
    */
   public String getFileExt() {
      return _fileExt;
   }

   /**
    * @param fileExt the fileExt to set
    */
   public void setFileExt(String fileExt) {
      _fileExt = fileExt;
   }

   /**
    * @return the openIds
    */
   public Map<String, OpenId> getOpenIds() {
      return _openIds;
   }

   /**
    * @param openIds the openIds to set
    */
   public void setOpenIds(Map<String, OpenId> openIds) {
      _openIds = openIds;
   }

   /**
    * @return the fileName
    */
   public String getFileName() {
      return _fileName;
   }

   /**
    * @param fileName the fileName to set
    */
   public void setFileName(String fileName) {
      _fileName = fileName;
   }

   /**
    * @return the openIdByUser
    */
   public Map<Integer, Collection<String>> getOpenIdByUser() {
      return _openIdByUser;
   }

   /**
    * @param openIdByUser the openIdByUser to set
    */
   public void setOpenIdByUser(Map<Integer, Collection<String>> openIdByUser) {
      _openIdByUser = openIdByUser;
   }

   /**
    * @return the writeToFile
    */
   public Boolean getWriteToFile() {
      return _writeToFile;
   }

   /**
    * @param writeToFile the writeToFile to set
    */
   public void setWriteToFile(Boolean writeToFile) {
      _writeToFile = writeToFile;
   }

}
